/*
 * This file is part of LibrePlan
 *
 * Copyright (C) 2009-2010 Fundación para o Fomento da Calidade Industrial e
 *                         Desenvolvemento Tecnolóxico de Galicia
 * Copyright (C) 2010-2011 Igalia, S.L.
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package org.libreplan.business.planner.limiting.entities;

import java.util.ArrayList;
import java.util.List;

import org.apache.commons.lang3.Validate;
import org.joda.time.LocalDate;
import org.libreplan.business.planner.entities.DayAssignment;
import org.libreplan.business.planner.entities.GenericDayAssignment;
import org.libreplan.business.planner.entities.GenericResourceAllocation;
import org.libreplan.business.planner.entities.ResourceAllocation;
import org.libreplan.business.planner.entities.SpecificDayAssignment;
import org.libreplan.business.planner.entities.SpecificResourceAllocation;
import org.libreplan.business.planner.limiting.entities.Gap.GapOnQueue;
import org.libreplan.business.resources.entities.LimitingResourceQueue;
import org.libreplan.business.resources.entities.Resource;
import org.libreplan.business.workingday.EffortDuration;

public abstract class AllocationSpec {

    public static AllocationSpec invalidOn(GapOnQueue gap) {
        return new InvalidAllocationAttempt(gap);
    }

    public static AllocationSpec validOn(
            LimitingResourceQueueElement element, GapOnQueue gap,
            DateAndHour start,
            DateAndHour endExclusive, int[] assignableHours) {
        return new ValidAllocationAttempt(element, gap, start, endExclusive,
                assignableHours);
    }

    private final GapOnQueue originalGap;

    private List<LimitingResourceQueueElement> unscheduled = new ArrayList<LimitingResourceQueueElement>();

    protected AllocationSpec(GapOnQueue originalGap) {
        Validate.notNull(originalGap);
        this.originalGap = originalGap;
    }

    public abstract boolean isValid();

    public abstract List<DayAssignment> getAssignmentsFor(
            ResourceAllocation<?> allocation, Resource resource)
            throws IllegalStateException;

    public abstract LimitingResourceQueueElement getElement()
            throws IllegalStateException;

    public abstract DateAndHour getStartInclusive()
            throws IllegalStateException;

    public abstract DateAndHour getEndExclusive() throws IllegalStateException;

    public Gap getGap() {
        return originalGap.getGap();
    }

    public LimitingResourceQueue getQueue() {
        return originalGap.getOriginQueue();
    }

    public boolean isAppropriative() {
        return !unscheduled.isEmpty();
    }

    public void setUnscheduledElements(
            List<LimitingResourceQueueElement> queueElements) {
        unscheduled.addAll(queueElements);
    }

    public List<LimitingResourceQueueElement> getUnscheduledElements() {
        return unscheduled;
    }

}

class InvalidAllocationAttempt extends AllocationSpec {

    private static final String INVALID_ALLOCATION_ON_GAP = "invalid allocation on gap";

    InvalidAllocationAttempt(GapOnQueue originalGap) {
        super(originalGap);
    }

    @Override
    public boolean isValid() {
        return false;
    }

    @Override
    public List<DayAssignment> getAssignmentsFor(
            ResourceAllocation<?> allocation, Resource resource) {
        throw new IllegalStateException(INVALID_ALLOCATION_ON_GAP);
    }

    @Override
    public LimitingResourceQueueElement getElement()
            throws IllegalStateException {
        throw new IllegalStateException(INVALID_ALLOCATION_ON_GAP);
    }

    @Override
    public DateAndHour getEndExclusive() throws IllegalStateException {
        throw new IllegalStateException(INVALID_ALLOCATION_ON_GAP);
    }

    @Override
    public DateAndHour getStartInclusive() throws IllegalStateException {
        throw new IllegalStateException(INVALID_ALLOCATION_ON_GAP);
    }
}

class ValidAllocationAttempt extends AllocationSpec {

    private final LimitingResourceQueueElement element;
    private final DateAndHour start;
    private final DateAndHour end;
    private final int[] assignableHours;

    public ValidAllocationAttempt(LimitingResourceQueueElement element,
            GapOnQueue gap, DateAndHour startInclusive,
            DateAndHour endExclusive, int[] assignableHours) {
        super(gap);
        Validate.notNull(element);
        Validate.notNull(startInclusive);
        Validate.notNull(endExclusive);
        Validate.notNull(assignableHours);
        Validate.isTrue(endExclusive.isAfter(startInclusive));
        this.element = element;
        this.start = startInclusive;
        this.end = endExclusive;
        Validate.isTrue(assignableHours.length == toFiniteList(
                start.daysUntil(end)).size());
        this.assignableHours = assignableHours.clone();
    }

    private List<LocalDate> toFiniteList(Iterable<LocalDate> daysUntil) {
        List<LocalDate> result = new ArrayList<LocalDate>();
        for (LocalDate each : daysUntil) {
            result.add(each);
        }
        return result;
    }

    @Override
    public List<DayAssignment> getAssignmentsFor(
            ResourceAllocation<?> allocation, Resource resource)
            throws IllegalStateException {
        List<LocalDate> days = toFiniteList(start.daysUntil(end));
        assert assignableHours.length == days.size();
        if (allocation instanceof SpecificResourceAllocation) {
            return createSpecific(days,
                    (SpecificResourceAllocation) allocation, resource);
        } else {
            return createGeneric(days, (GenericResourceAllocation) allocation,
                    resource);
        }
    }

    private List<DayAssignment> createSpecific(List<LocalDate> days,
            SpecificResourceAllocation allocation, Resource resource) {
        List<DayAssignment> result = new ArrayList<DayAssignment>();
        int i = 0;
        for (LocalDate each : days) {
            EffortDuration hours = EffortDuration.hours(assignableHours[i]);
            result.add(SpecificDayAssignment.create(each, hours, resource));
            i++;
        }
        return result;
    }

    private List<DayAssignment> createGeneric(List<LocalDate> days,
            GenericResourceAllocation allocation, Resource resource) {
        List<DayAssignment> result = new ArrayList<DayAssignment>();
        int i = 0;
        for (LocalDate each : days) {
            EffortDuration hours = EffortDuration.hours(assignableHours[i]);
            result.add(GenericDayAssignment.create(each, hours, resource));
            i++;
        }
        return result;
    }

    @Override
    public LimitingResourceQueueElement getElement() {
        return element;
    }

    @Override
    public DateAndHour getEndExclusive() throws IllegalStateException {
        return end;
    }

    @Override
    public DateAndHour getStartInclusive() throws IllegalStateException {
        return start;
    }

    @Override
    public boolean isValid() {
        return true;
    }

}
